# coding=utf-8
"""
author:董新强
description:token校验
"""
import binascii
import hashlib
import logging
import os
import time as timepy
from datetime import datetime

from tornado.gen import coroutine

from auth import LOGINTOKENAESKEY, ADMIN_USER_ROLE, APPRAISER_USER_ROLE, COMPANY_USER_ROLE, PERSON_USER_ROLE
from auth.enum_client import EnumClient
from auth.utils import to_int, get_random_chars
###################################################
from frameworks.aes_cypto import AesCrypto
from frameworks.db_session import DbSession
from frameworks.redis_helper import ReidsHelper
from frameworks.resonse_msgs import fail, ok
from models.ceping import UserLogin, Users
from pools import run_async


class TokenManage:
    @staticmethod
    @coroutine
    def validate_token(token, app_key):
        if not token:
            return fail(msg='请登录', hint='token错误')

        cache = ReidsHelper()

        try:
            token = token.split()
            if len(token) != 2: return fail(msg='请登录', hint='token格式错误')
            token = token[-1]

            # 获取token及用户信息
            uid, expire, token, company_id, client, union_id = TokenManage.decrypt_token(token)
            redis_key = 'auth_{}_{}'.format(uid, client)

            login_info = yield TokenManage.get_user_login_info(cache, redis_key, client, uid)
            if not login_info:
                return fail(msg='请登录', hint='用户不存在')

            # 验证token
            r_expire, r_key = TokenManage.split_login_info(login_info)
            if not r_expire: return fail(msg='请登录', hint='登录过期')

            if r_expire < expire:  # 现在的token为最新的,重新从数据库载入数据
                login_info = yield TokenManage._reload_user_login_info(cache, client, redis_key, uid)
                if not login_info:
                    return fail(msg='请登录', hint='用户不存在')
                r_expire, r_key = TokenManage.split_login_info(login_info)
            elif r_expire > expire:  # token的时间已经过期
                return fail(msg='请登录', hint='登录过期')

            # 比对token
            if token != TokenManage._gen_login_token(uid, expire, r_key, company_id):
                return fail(msg='请登录', hint='token错误')

            data = {
                'id': uid,
                'cmpid': company_id,
                'client': client,
                'role': TokenManage._get_role(client),
                'union_id': union_id
            }
            return ok(data, app_key=app_key)
        except Exception as e:
            return fail(msg='请登录', hint=str(e))
        finally:
            cache.close()

    @staticmethod
    def split_login_info(login_info):
        login_info = login_info.split()
        r_expire, r_key = int(login_info[0]), login_info[1]
        return r_expire, r_key

    @staticmethod
    @coroutine
    def get_user_login_info(cache, redis_key, client, uid):
        '''
        获取用户登陆信息
        :param client: 登陆端
        :param uid: 用户ID
        :return: expire|login_key
        '''
        # 从缓存中获取登陆信息

        login_info = yield cache.get(redis_key)
        if login_info:
            return login_info

        # 从数据库获取登陆信息
        login_info = yield TokenManage._reload_user_login_info(cache, client, redis_key, uid)
        return login_info

    @staticmethod
    @coroutine
    def _reload_user_login_info(cache, client, redis_key, uid):
        # 从数据库载入用户登陆信息
        login_info = yield run_async(TokenManage._get_login_info_from_db, uid, client)
        if not login_info:
            return None

        # 将信息写入缓存
        yield cache.set(redis_key, login_info)
        return login_info

    @staticmethod
    def decrypt_token(token):
        '''
        解密token
        '''
        dic = {}
        for kv in AesCrypto(encoding_aes_key=LOGINTOKENAESKEY).decrypt(token).split('&'):
            k, v = kv.split('=')
            dic[k] = v

        uid = to_int(dic.get('u'), 0)
        expire = to_int(dic.get('e'), 0)
        token = dic.get('t')
        company_id = to_int(dic.get('cid'), 0)
        client = to_int(dic.get('c'), 0)  # 登陆端 2019-7-2
        union_id = to_int(dic.get('union'))
        return uid, expire, token, company_id, client, union_id

    @staticmethod
    def _get_login_info_from_db(uid, client):
        '''
        从数据库获取用户登陆信息
        :param uid: 用户ID
        :param client: 登陆端
        :return: expire|login_key
        '''
        with DbSession.create() as db:
            login_info = db.query(UserLogin.expire, UserLogin.key).filter(UserLogin.uid == uid).filter(UserLogin.client == client).first()
        if not login_info:
            return None

        return '{} {}'.format(login_info[0], login_info[1])

    @staticmethod
    def _get_role(client):
        if client == EnumClient.admin.value:
            return ADMIN_USER_ROLE
        if client == EnumClient.appra.value:
            return APPRAISER_USER_ROLE
        if client == EnumClient.company.value:
            return COMPANY_USER_ROLE
        return PERSON_USER_ROLE  # 默认都是个人账号

    @staticmethod
    def generate_django_token():
        return binascii.hexlify(os.urandom(20)).decode()

    @staticmethod
    def _gen_login_token(uid, expire, key, company_id):
        uid = str(uid)
        m = hashlib.md5('{0}-{1}-{2}'.format(uid, str(expire + company_id), key).encode("utf8"))
        m.update(b'^&)(+":|}{:kkJJ443~``++adsfas*)1`12`1sdas')
        m.update('{1}{0}'.format(uid, expire).encode("utf8"))
        return m.hexdigest()

    @staticmethod
    def gen_login_token(app_key, uid, client):
        """
        换取登录token
        """
        # 初始化相关参数
        with DbSession.create() as db:
            rsa_key, expire = TokenManage.save_login_info(db, uid, client)
            union_id = TokenManage.get_union_id(uid, db)
        token = TokenManage.gen_token(uid, expire, rsa_key, client, union_id)
        return ok(token, app_key)

    @staticmethod
    def save_login_info(db, uid, client):
        rsa_key = get_random_chars(24)
        expire = int(timepy.time()) + 30 * 24 * 3600  # 30天后过期

        login_info = db.query(UserLogin).filter(UserLogin.uid == uid).filter(UserLogin.client == client).first()
        if login_info:
            login_info.expire = expire
            login_info.key = rsa_key
            login_info.login_time = datetime.now()
        else:
            login_info = UserLogin()
            login_info.uid = uid
            login_info.client = client
            login_info.expire = expire
            login_info.key = rsa_key
            login_info.login_time = datetime.now()
            db.add(login_info)
        return rsa_key, expire

    @staticmethod
    def gen_token(uid, expire, rsa_key, client, union_id=0):
        if not union_id: union_id = 0

        token = TokenManage._gen_login_token(uid, expire, rsa_key, 0)
        raw_str = 'u=%d&e=%d&t=%s&cid=%d&c=%d&union=%d' % (uid, expire, token, 0, client, union_id)
        token = AesCrypto(encoding_aes_key=LOGINTOKENAESKEY).encrypt(raw_str)
        return token

    @staticmethod
    def get_union_id(uid, db):
        try:
            union_id = db.query(Users.union_id).filter(Users.id == uid).first()
            if not union_id: return 0
            return union_id[0]
        except Exception as e:
            logging.error('token.get_union_id.e', e)
            return 0

    @staticmethod
    def gen_login_token_with_others(app_key, uid, client):
        """
        换取登录token
        """
        # 初始化相关参数
        with DbSession.create() as db:
            rsa_key, expire = TokenManage.save_login_info(db, uid, client)
            union_id = TokenManage.get_union_id(uid, db)
        token = TokenManage.gen_token(uid, expire, rsa_key, client, union_id)
        return ok({'token': token, 'union_id': union_id}, app_key)
